public class Instruction {

	// store labels if there is any.
	private String symb = null;

	// store operations.
	private String ops = null;

	// store operands.
	private String[] operand = new String[3];

	// store line number associated with current line.
	private Integer lineNum = 0;

	// store corresponding memory addres.
	private Integer memAddr = null;

	/**
	 * Constructor.
	 * 
	 * @param line
	 * @throws AssembleException
	 */
	public Instruction(String line, Integer lineNum) throws AssembleException {
		// check for no .ORIG error.
		if (!Lines.hasORIG() && !line.contains("ORIG")) {
			throw new AssembleException(1);
		}

		// get symbol.
		symb = line.substring(0, 6).trim();

		if (line.length() > 14) {// deal with operations that has operands.
			ops = line.substring(9, 14).trim();
		} else {// deal with operations that has no operands such as RET.
			ops = line.substring(9);
		}
		this.lineNum = lineNum;
		if (ops.equals(".STRZ")) {
			String str = line.substring(17).trim();
			operand[0] = str;

			// check for string no enclosed by "" error.
			if (str.charAt(0) != '\"' || str.charAt(str.length() - 1) != '\"') {
				throw new AssembleException(9, lineNum, null);
			}

		} else if (ops.equals(".ORIG")) {
			if (line.length() == 14) {// relocatable
				operand[0] = "x0";
				Lines.setRel();
			} else {// absolute
				operand = line.substring(17).trim().split("[,]\\s*");
				// check for number of operands error.
				if (operand.length != 1) {
					throw new AssembleException(4, lineNum, ops,
							operand.length, 1);
				}
				if (operand[0].charAt(0) != 'x') {
					throw new AssembleException(
							0,
							"The operand of .ORIG did not start with a hex value address when the program is absolute.");
				}else {
					if(Utilities.toDecimal(operand[0])>65535 || Utilities.toDecimal(operand[0])<0) {
						throw new AssembleException(0,"Starting location must be in the range of x0 and xFFFF");
					}
				}
				
				Integer val = Utilities.toDecimal(operand[0]);

				// check for syntax of operand
				if (val == null) {
					throw new AssembleException(
							0,
							"Operand of .ORIG should be values only,but"
									+ " a symbol is used (also might be a value with no hex or dec prefix).");
				} else {// check for overflow.
					if (val > 65535 || val < 0) {
						throw new AssembleException(5, lineNum, operand[0],
								"x0", "xFFFF");
					}
				}

			}
		} else if (ops.equals("RET") || ops.equals("DBUG") || ops.equals("NOP")) {

			line = line.trim(); // FIXME these ops can have labels.

			if (line.length() > 14) {
				operand = line.substring(3).trim().split("[,]\\s*");
				throw new AssembleException(4, lineNum, ops, operand.length, 0);
			}
		} else {
			if (line.length() > 17) {
				operand = line.substring(17).trim().split("[,]\\s*");
			} else {
				operand = "".split("[,]\\s*");
			}
		}

		// TODO updated for lab3,update UG and PG plz.

		// store to EXT table.
		if (ops.equals(".EXT")) {
			for (String val : operand) {
				EXTTable.add(val, lineNum);
			}
		}

		// store to ENT table
		if (ops.equals(".ENT")) {
			for (String val : operand) {
				ENTTable.add(val, lineNum);
			}
		}

		// TODO end of update for lab3.

		// check for number of operands error.
		if (ops.equals(".FILL") || ops.equals("TRAP") || ops.equals("JSR")
				|| ops.equals("JMP") || ops.equals(".EQU")
				|| ops.equals(".BLKW") || (ops.substring(0, 2).equals("BR"))) {
			if (operand.length == 1 && operand[0].equals("")) {
				throw new AssembleException(4, lineNum, ops, 0, 1);
			} else if (operand.length != 1) {
				throw new AssembleException(4, lineNum, ops, operand.length, 1);
			}
		} else if (ops.equals("JSRR") || ops.equals("JMPR") || ops.equals("LD")
				|| ops.equals("LDI") || ops.equals("LEA") || ops.equals("NOT")
				|| ops.equals("ST") || ops.equals("STI")) {
			if (operand.length != 2) {
				throw new AssembleException(4, lineNum, ops, operand.length, 2);
			}

		} else if (ops.equals("ADD") || ops.equals("AND") || ops.equals("LDR")
				|| ops.equals("STR")) {
			if (operand.length != 3) {
				if (operand[0].equals("")) {
					throw new AssembleException(4, lineNum, ops, 0, 3);
				} else {
					throw new AssembleException(4, lineNum, ops,
							operand.length, 3);
				}
			}
		} else if (ops.equals(".END")) {
			if (operand.length > 1) {
				throw new AssembleException(4, lineNum, ops, operand.length, 1);
			}
			// TODO updated for lab3, update UG and PG if necessary.
		} else if (ops.equals(".EXT") || ops.equals(".ENT")) {
			if (operand.length == 1 && operand[0].equals("")) {
				throw new AssembleException(0,
						"Operation .EXT and .ENT must have at least one operands but was 0 at line "
								+ lineNum);
			}
		}
		// TODO end of update for lab3.

	}

	public void setAddr(int locCtr) {
		memAddr = locCtr;
	}

	public int getAddr() {
		if (this.memAddr != null) {
			return this.memAddr;
		} else {
			return -1;
		}
	}

	public boolean hasSymb() throws AssembleException {
		if (symb.length() != 0) {
			if (ops.equals(".END")) {
				throw new AssembleException(0,
						".END operation must not has a label.");
			}
			return true;
		} else {
			// .ORIG and .EQU must have symbols.
			if (ops.equals(".ORIG") || ops.equals(".EQU")) {
				throw new AssembleException(11, lineNum, ops);
			}

			return false;
		}
	}

	public int memTaken() {
		// return memory taken by a operation.

		if (ops.equals(".EQU") || ops.equals(".END") || ops.equals(".ORIG")
				|| ops.equals(".EXT") || ops.equals(".ENT")) {
			return 0;
		} else if (ops.equals(".BLKW")) {
			if (operand[0].charAt(0) == '#') {
				return Integer.parseInt(operand[0].substring(1));
			} else {// if (operand[0].charAt(0)=='x'){
				return Integer.parseInt(operand[0].substring(1), 16);
			}
		} else if (ops.equals(".STRZ")) {
			return operand[0].length() - 1;
		} else {
			return 1;
		}
	}

	public int getLineNum() {
		return lineNum;
	}

	public String getSymb() {
		return symb;
	}

	public String getOps() {
		return ops;
	}

	public String[] getOperand() {
		return operand;
	}

	public String relocate() throws AssembleException {
		// return string used to append to output when need to mark relocatable
		// text records.

		if (ops.equals(".FILL")) {
			if (operand[0].charAt(0) != 'x' && operand[0].charAt(0) != '#') {
				if (SymbTable.hasSym(operand[0])) {
					boolean isConst = SymbTable.isConst(operand[0]);
					if (!isConst) {
						return Lines.isRel() ? " M16" : "";
						// return Lines.isRel()?" X16"+Lines.getSegName():"";
					}
				} else if (EXTTable.hasSymb(operand[0])) {
					return Lines.isRel() ? " X16" + operand[0] : "";
				} else {
					throw new AssembleException(0,
							"Error appear at Instruction::relocate() first branch");
				}
			}
		} else if (ops.equals("LDI") || ops.equals("ST") || ops.equals("LD")
				|| ops.equals("LEA") || ops.equals("STI")) {
			if (operand[1].charAt(0) != 'x' && operand[1].charAt(0) != '#'
					&& operand[1].charAt(0) != '=') {
				if (SymbTable.hasSym(operand[1])) {
					boolean isConst = SymbTable.isConst(operand[1]);
					if (!isConst) {
						return Lines.isRel() ? " M9" : "";
						// return Lines.isRel()?" X9"+Lines.getSegName():"";
					}
				} else if (EXTTable.hasSymb(operand[1])) {
					return Lines.isRel() ? " X9" + operand[1] : "";
				} else {
					throw new AssembleException(0,
							"Error appear at Instruction::relocate()2nd branch");
				}
			}
		} else if (ops.charAt(0) == 'B' || ops.equals("JSR")
				|| ops.equals("JMP") || ops.equals("TRAP")) {
			if (operand[0].charAt(0) != 'x' && operand[0].charAt(0) != '#') {
				if (SymbTable.hasSym(operand[0])) {
					boolean isConst = SymbTable.isConst(operand[0]);
					if (!isConst) {
						return Lines.isRel() ? " M9" : "";
						// return Lines.isRel()?" X9"+operand[0]:"";
					}
				} else if (EXTTable.hasSymb(operand[0])) {
					return Lines.isRel() ? " X9" + operand[0] : "";
				} else {
					throw new AssembleException(0,
							"Error appear at Instruction::relocate()3rd branch");
				}
			}
		}

		return "";
	}

}
